<?php

namespace app\common\storage;

use app\admin\model\StorageChunks;
use app\admin\model\StoragePath;
use think\facade\Log;

class WriterStorage extends CommonStorage
{
    protected $modelPath;
    protected $data;
    protected $rangeType;
    protected $offset;
    protected $sort = -1;
    protected $fileSize = 0;
    protected $chunkSizeSet = CommonStorage::CHUNK_SIZE_SET;
    public function __construct(StoragePath $model_path, $data, $rangeType = null, $offset = null)
    {
        $this->modelPath = $model_path;
        $this->data = $data;
        $this->rangeType = $rangeType;
        $this->offset = $offset;
        $this->getFlysystem();
    }

    /**
     * 全新写入
     *
     * @return void
     */
    public function save()
    {

        // 从流中分块数据

        $this->sort = -1;
        $this->fileSize = 0;
        if (!empty($this->data)) {

            if (is_resource($this->data)) {
                $this->saveInStream();
            } else if (is_string($this->data)) {
                $this->saveInString();
            }
        }



        $this->deleteLastSortChunks();

        $space = $this->chunkSizeSet * ($this->sort + 1);

        if ($space < 0) {
            $space = 0;
        }

        $this->modelPath->size = $this->fileSize;
        $this->modelPath->space = $space;
    }

    public function deleteLastSortChunks()
    {
        // 分块数据读取完成，将如果存在旧的大于sort的块删除
        $list_trash_chunks = StorageChunks::where('storage_path_id', $this->modelPath->id)
            ->where('sort', '>', $this->sort)
            ->select();


        foreach ($list_trash_chunks as $model_chunks) {
            $this->deleteChunk($model_chunks->chunk_md5);
            $model_chunks->delete();
        }
    }

    protected function saveInStream()
    {
        $chunk_list_md5_content = '';

        $handle = $this->data;

        rewind($handle);

        $chunk_content_temp = '';

        while (!feof($handle)) {

            // 计算分块MD5，并与旧的块对比，如果不同或旧的块不存在，则存储为新的块

            $chunk_content = fread($handle, 1024);

            $chunk_content_temp .= $chunk_content;

            if (strlen($chunk_content_temp) <= $this->chunkSizeSet) {
                continue;
            }

            $this->sort++;


            $model_chunks = $this->writeChunk($chunk_content_temp, $this->sort);
            $chunk_size = strlen($chunk_content_temp);
            $chunk_list_md5_content .= $model_chunks->chunk_md5;
            $this->fileSize += $chunk_size;
            $chunk_content_temp = '';
        }

        if (!empty($chunk_content_temp)) {
            $this->sort++;
            $model_chunks = $this->writeChunk($chunk_content_temp, $this->sort);
            $chunk_size = strlen($chunk_content_temp);
            $chunk_list_md5_content .= $model_chunks->chunk_md5;

            $this->fileSize += $chunk_size;
        }

        $this->modelPath->chunk_list_md5 = md5($chunk_list_md5_content);

        if ($this->checkFreeSpace($this->fileSize)) {

            $file_temp_path = $this->buildFileTempPath($this->modelPath->chunk_list_md5);

            // 在本地做一次完整的缓存
            $local_cache_handle = fopen($file_temp_path, 'wb+');

            // 在本地的缓存流中写入一份
            rewind($handle);
            stream_copy_to_stream($handle, $local_cache_handle);

            fclose($local_cache_handle);

            $content_type = mime_content_type($file_temp_path);

            $this->modelPath->content_type = $content_type;

        }
        fclose($handle);
    }

    protected function saveInString()
    {
        $chunk_list_md5_content = '';

        $data_arr = str_split($this->data,$this->chunkSizeSet);

        foreach ($data_arr as  $chunk_content_temp) {
            $this->sort++;

            $model_chunks = $this->writeChunk($chunk_content_temp, $this->sort);
            $chunk_size = strlen($chunk_content_temp);
            $this->fileSize += $chunk_size;
            $chunk_content_temp = '';

            $chunk_list_md5_content .= $model_chunks->chunk_md5;
        }

        $this->modelPath->chunk_list_md5 = md5($chunk_list_md5_content);

        if ($this->checkFreeSpace($this->fileSize)) {

            $file_temp_path = $this->buildFileTempPath($this->modelPath->chunk_list_md5);

            // 在本地做一次完整的缓存
            $local_cache_handle = fopen($file_temp_path, 'wb+');

            // 在本地的缓存流中写入一份
            

            fwrite($local_cache_handle,$this->data);

            fclose($local_cache_handle);

            $content_type = mime_content_type($file_temp_path);

            $this->modelPath->content_type = $content_type;

        }

    }

    /**
     * 部分写入
     *
     * @return void
     */
    public function savePatch()
    {

        $rangeType = $this->rangeType;
        $offset = $this->offset;
        $this->fileSize = 0;
        $data = $this->data;


        // 定位chunk位置


        // 更新chunk信息

        // 更新完整文件缓存
        // 更新完整etag
        // 更新chunk_list_md5

        $list_chunks = StorageChunks::where('storage_path_id', $this->modelPath->id)
            ->order('sort', 'asc')
            ->select();

        if (empty($list_chunks)) {
            // 没有任何chunks，则实际上是全新写入
            $this->save();
            return true;
        }

        // 可用的文件大小是可用chunks size
        // 最后一个chunksize 如果被截取了，则会在写入时通过prefix_data计入
        $this->fileSize += ($list_chunks->count() - 1) * $this->chunkSizeSet;

        $prefix_data = '';

        switch ($rangeType) {
                // case 1 ，默认追加
            case 2:
                // 定位到从开始的某个位置
                $start_sort = floor($offset / $this->chunkSizeSet);

                if ($start_sort > $list_chunks->count()) {
                    throw new \Exception("patch 定位 错误", 1);
                }

                $start_chunk = $list_chunks[$start_sort];

                $start_chunk_offset = $offset % $this->chunkSizeSet;

                if (!empty($start_chunk_offset)) {

                    $chunk_content = $this->readChunk($start_chunk->chunk_md5);

                    $chunk_content_size = strlen($chunk_content);
                    if ($chunk_content_size < $this->chunkSizeSet) {
                        // 刚好是最后一个chunk

                        if ($chunk_content_size < $start_chunk_offset) {
                            // 起始offset比结尾还要大，出错了

                            throw new \Exception("patch offset 超出实际内容", 1);
                        }
                    }

                    $prefix_data = substr($chunk_content, 0, $start_chunk_offset);
                } else {
                    $this->fileSize += $this->chunkSizeSet;
                }
                $this->sort = $start_chunk->sort;

                break;
            case 3:
                // 定位到从结束为止的某个位置
                $list_chunks_reversed = $list_chunks->reverse();

                $start_sort = floor($offset / $this->chunkSizeSet);

                if ($start_sort > $list_chunks_reversed->count()) {
                    throw new \Exception("patch 定位 错误", 1);
                }

                $start_chunk = $list_chunks_reversed[$start_sort];

                $start_chunk_offset = $offset % $this->chunkSizeSet;

                if (!empty($start_chunk_offset)) {

                    $chunk_content = $this->readChunk($start_chunk->chunk_md5);

                    $chunk_content_size = strlen($chunk_content);
                    if ($chunk_content_size < $this->chunkSizeSet) {
                        // 刚好是最后一个chunk

                        if ($chunk_content_size < $start_chunk_offset) {
                            // 起始offset比结尾还要大，出错了

                            throw new \Exception("patch offset 超出实际内容", 1);
                        }
                    }

                    $prefix_data = substr($chunk_content, 0, $start_chunk_offset);
                } else {
                    $this->fileSize += $this->chunkSizeSet;
                }
                $this->sort = $start_chunk->sort;
                break;
            default:
                // 追加

                $last_model_chunk = $list_chunks->last();

                $chunk_content = $this->readChunk($last_model_chunk->chunk_md5);

                $last_chunk_size = strlen($chunk_content);

                if ($last_chunk_size < $this->chunkSizeSet) {
                    // 从最后一个拼接
                    $prefix_data = $chunk_content;

                    $this->sort = $last_model_chunk->sort;
                } else {
                    $this->sort = $last_model_chunk->sort + 1;
                }

                break;
        }


        if (is_string($data)) {
            $this->patchInString($list_chunks, $prefix_data);
        } else {
            $this->patchInStream($list_chunks, $prefix_data);
        }

        $chunk_list_md5_arr = $list_chunks->column('chunk_md5');

        $chunk_list_md5 = md5(implode('', $chunk_list_md5_arr));

        // 删除旧的缓存
        $file_temp_path = $this->buildFileTempPath($this->modelPath->chunk_list_md5);
        @unlink($file_temp_path);

        $this->modelPath->chunk_list_md5 = $chunk_list_md5;

        $this->deleteLastSortChunks();


        $this->patchWithCache($data, $rangeType, $offset);
    }

    protected function patchWithCache($data, $rangeType, $offset = null)
    {
        $file_temp_path = $this->buildFileTempPath($this->modelPath->chunk_list_md5);

        if(!file_exists($file_temp_path)){
            return false;
        }

        switch ($rangeType) {
            case 1:
                $f = fopen($file_temp_path, 'a');
                break;
            case 2:
                $f = fopen($file_temp_path, 'c');
                fseek($f, $offset);
                break;
            case 3:
                $f = fopen($file_temp_path, 'c');
                fseek($f, $offset, SEEK_END);
                break;
            default:
                $f = fopen($file_temp_path, 'a');
                break;
        }

        if (is_string($data)) {
            fwrite($f, $data);
        } else {
            rewind($data);
            stream_copy_to_stream($data, $f);
        }
        fclose($f);
        clearstatcache(true, $file_temp_path);
    }

    protected function patchInStream($list_chunks, $prefix_data = '')
    {

        $this->sort--;

        $handle = $this->data;

        rewind($handle);

        $chunk_content_temp = $prefix_data;

        $read_size_set = $this->chunkSizeSet;

        $read_size_set = $read_size_set - strlen($chunk_content_temp);

        if ($read_size_set > 1024) {
            $read_size_set = 1024;
        }

        while (!feof($handle)) {

            // 计算分块MD5，并与旧的块对比，如果不同或旧的块不存在，则存储为新的块

            $chunk_content = fread($handle, $read_size_set);

            $read_size_set = 1024;

            $chunk_content_temp .= $chunk_content;

            if (strlen($chunk_content_temp) <= $this->chunkSizeSet) {
                continue;
            }

            $this->sort++;

            $model_chunks = $this->writeChunk($chunk_content_temp, $this->sort);
            $chunk_size = strlen($chunk_content_temp);
            $this->fileSize += $chunk_size;
            $chunk_content_temp = '';
            $list_chunks->push($model_chunks);
        }

        if (!empty($chunk_content_temp)) {
            $this->sort++;
            $model_chunks = $this->writeChunk($chunk_content_temp, $this->sort);
            $chunk_size = strlen($chunk_content_temp);

            $this->fileSize += $chunk_size;
            $list_chunks->push($model_chunks);
        }

       
    }

    protected function patchInString($list_chunks, $prefix_data = '')
    {

        $this->sort--;

        $this->data = $prefix_data.$this->data;

        $data_arr = str_split($this->data,$this->chunkSizeSet);

        foreach ($data_arr as  $chunk_content_temp) {
            $this->sort++;

            $model_chunks = $this->writeChunk($chunk_content_temp, $this->sort);
            $chunk_size = strlen($chunk_content_temp);
            $this->fileSize += $chunk_size;
            $chunk_content_temp = '';
            $list_chunks->push($model_chunks);
        }

    }
}
